package fag.core.game.test.arkanoid;

import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.JFrame;

import fag.core.collision.Collision;
import fag.core.game.AbstractGame;
import fag.core.game.Transformable.Referential;
import fag.core.geometry.Vector2D;

public class Arkanoid extends AbstractGame implements MouseMotionListener, MouseListener {

	public static final int WIDTH = 599;
	public static final int HEIGHT = 700;
	
	/**
	 * Point d'entrée du programme.
	 * @param args Les arguments du programme.
	 */
	public static void main(String[] args) {
		
		Arkanoid arkanoid = new Arkanoid();
		arkanoid.start();
		
		JFrame frame = new JFrame("Arkanoid");
		frame.setSize(WIDTH, HEIGHT);
		frame.setResizable(false);
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		frame.add(arkanoid.getCamera());
		frame.setCursor(frame.getToolkit().createCustomCursor(new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB), new Point(0, 0), "null"));
		frame.pack();
	}

	public static final int GAME_HEIGHT = 50;
	public static final int RACKET_OFFSET = (WIDTH * 80) / 100;
	public static final int LIFE_OFFSET = HEIGHT - 20;
	
	public static final double BONUS_PROBABILITY = 0.2;
	public static final double FIRE_INTERVAL = 300.0;
	
	private boolean attachBalls = false;
	private boolean fireLaser = false;
	private boolean freeze = false;
	private boolean mousePressed = false;
	private boolean firstSend = true;
	
	private int life = 3;
	private int level = 1;
	private int score = 0;
	
	// Camera et Scene
	private ArkanoidHud camera = new ArkanoidHud();	
	private ArkanoidScene scene = new ArkanoidScene();
	
	// Timers
	private double deathTimer = 0;
	private double fireTimer = 0;
	
	// Doodads
	private Racket racket = new Racket();
	private List<Ball> balls = new ArrayList<Ball>();
	private List<Block> blocks = new ArrayList<Block>();
	private List<Bonus> bonuses = new ArrayList<Bonus>();
	private List<Laser> lasers = new ArrayList<Laser>();
	private List<Racket> lifes = new ArrayList<Racket>();
	
	/**
	 * Constructeur du jeu.
	 * Initialisation du niveau et des doodads.
	 */
	public Arkanoid() {
		
		addScene(scene);
		scene.addCamera(camera);
		camera.setSize(WIDTH, HEIGHT);
		
		// Raquette
		scene.addDoodad(racket);
		
		// Lifes
		for(int i = 0; i < life; i++) {
			Racket newLife = new Racket();
			newLife.setAnimation("mini");
			newLife.setTranslation(i * 30, LIFE_OFFSET);
			lifes.add(newLife);
			scene.addDoodad(newLife);
		}
		
		// Listeners
		camera.addMouseListener(this);
		camera.addMouseMotionListener(this);
		
		buildLevel(1);
		restartRacket();
	}
	
	public List<Block> getBlocks() {
		return blocks;
	}
	
	public ArkanoidHud getCamera() {
		return camera;
	}
	
	@Override
	public void step(double dt) {
		
		// Gestion du freeze
		if(freeze) {
			deathTimer += dt;
			if(deathTimer >= 600) {
				freeze = false;
				restartRacket();
			}
			return;			
		}
		
		// Recommencer le jeu
		if(balls.isEmpty()) {
			cleanLevel();
			if(life == 0)
				gameOver();
			else {
				life--;
				Racket lostLife = lifes.get(lifes.size() - 1);
				scene.removeDoodad(lostLife);
				lifes.remove(lifes.size() - 1);
			}
			return;
		}
		
		// Niveau suivant
		if(blocks.isEmpty() && level <= LevelManager.FINAL_LEVEL) {
			nextLevel();
			return;
		}
		
		// Tirer un laser
		if(fireLaser && mousePressed) {			
			if(fireTimer >= FIRE_INTERVAL) {			
				fireTimer = 0;
				
				Laser l1 = new Laser();
				Laser l2 = new Laser();
				
				l1.translate(racket.getTranslationX(), racket.getTranslationY(), Referential.ABSOLUTE);
				l2.translate(racket.getTranslationX() + 20, racket.getTranslationY(), Referential.ABSOLUTE);
				
				lasers.add(l1);
				lasers.add(l2);
				scene.addDoodad(l1);
				scene.addDoodad(l2);
			}
			else
				fireTimer += dt;
		}
		
		Iterator<Ball> itBall = balls.iterator();
		Iterator<Block> itBlock = blocks.iterator();
		Iterator<Laser> itLaser = lasers.iterator();
		
		// Blocks
		while(itBlock.hasNext()) {
			Block block = itBlock.next();
			
			// Collisions avec les balles
			itBall = balls.iterator();
			while(itBall.hasNext()) {
				Ball a = itBall.next();				
				Collision collision = a.getHitbox().getCollision(block.getHitbox());
				
				if(collision.isColliding()) {					
					Vector2D normal = collision.getNormal();
					Vector2D separator = normal.mul(collision.getPenetration() * 0.5).add(normal);
					a.translate(separator, Referential.ABSOLUTE);
					
					a.bounce(normal.mul(-1));
					
					// Ajout du score
					addScore(block.score);
					
					// Bonus wheel
					if(Math.random() < BONUS_PROBABILITY) {
						Bonus bonus = new Bonus();
						bonuses.add(bonus);
						scene.addDoodad(bonus);
						bonus.setTranslation(block.getTranslation());
					}
					
					itBlock.remove();
					scene.removeDoodad(block);
				}
			}
		}
		// Blocks
		itBlock = blocks.iterator();
		boolean blockRemoved = false;
		while(itBlock.hasNext()) {
			Block block = itBlock.next();
				
			// Collision avec les lasers
			itLaser = lasers.iterator();
			blockRemoved = false;
			while(itLaser.hasNext() && !blockRemoved) {
				Laser laser = itLaser.next();
				
				Collision collision = block.getHitbox().getCollision(laser.getHitbox());
				
				if(collision.isColliding()) {
					blockRemoved = true;
					itBlock.remove();
					scene.removeDoodad(block);
					itLaser.remove();
					scene.removeDoodad(laser);
				}
			}
		}
		
		// Collisions entre les balles
		for(int i = 0; i < balls.size() - 1; ++i) {
			for(int j = i + 1; j < balls.size(); ++j) {
				
				Ball a = balls.get(i);
				Ball b = balls.get(j);
				
				Collision collision = a.getHitbox().getCollision(b.getHitbox());
				
				if(collision.isColliding()) {
					
					Vector2D normal = collision.getNormal();
					Vector2D separator = normal.mul(collision.getPenetration() * 0.5).add(normal);
					a.translate(separator.mul(-1), Referential.ABSOLUTE);
					b.translate(separator, Referential.ABSOLUTE);
					a.bounce(normal);
					b.bounce(normal.mul(-1));
				}
			}
		}
		
		// Collisions entre les balles et la raquette
		itBall = balls.iterator();
		
		while(itBall.hasNext()) {
			Ball ball = itBall.next();			
			Collision collision = racket.getHitbox().getCollision(ball.getHitbox());			
			if(collision.isColliding() && !ball.attached) {
				
				if(attachBalls) {
					ball.attached = true;
				}
				else {
					Vector2D normal = collision.getNormal();
					Vector2D separator = normal.mul(collision.getPenetration() * 1).add(normal);
					ball.translate(separator, Referential.ABSOLUTE);
					ball.bounce(normal.mul(-1));
				}
			}
		}
		
		// Collision entre les bonus et la raquette
		Iterator<Bonus> itBonus = bonuses.iterator();
		while(itBonus.hasNext()) {
			Bonus bonus = itBonus.next();			
			Collision collision = racket.getHitbox().getCollision(bonus.getHitbox());
			if(collision.isColliding()) {				
				obtainBonus(bonus);
				scene.removeDoodad(bonus);
				itBonus.remove();
			}
		}
		
		// Balles
		itBall = balls.iterator();
		while(itBall.hasNext()) {
			Ball ball = itBall.next();
			
			// Collisions avec le bord de l'écran
			if(ball.getTranslationX() < 0) {
				ball.setTranslation(1, ball.getTranslationY());
				ball.bounce(new Vector2D(1, 0));
			}
			else if(ball.getTranslationX() > WIDTH) {
				ball.setTranslation(WIDTH - 16 - 1, ball.getTranslationY());
				ball.bounce(new Vector2D(-1, 0));
			}
			if(ball.getTranslationY() < GAME_HEIGHT + 5) {
				ball.setTranslation(ball.getTranslationX(), GAME_HEIGHT + 5);
				ball.bounce(new Vector2D(0, 1));
			}
			else if(ball.getTranslationY() > HEIGHT) {
				scene.removeDoodad(ball);
				itBall.remove();
				continue;
			}
			
			// Déplacer les balles
			ball.step(dt / 1000);
		}
		
		// Bonus
		itBonus = bonuses.iterator();
		while(itBonus.hasNext()) {
			Bonus bonus = itBonus.next();
			
			// Disparition de l'écran
			if(bonus.getTranslationY() > HEIGHT - 10) {
				scene.removeDoodad(bonus);
				itBonus.remove();
				continue;
			}
			
			bonus.step(dt / 1000);			
		}
		
		// Lasers
		itLaser = lasers.iterator();
		while(itLaser.hasNext()) {
			Laser laser = itLaser.next();
			
			// Disparition de l'écran
			if(laser.getTranslationY() < 0) {
				scene.removeDoodad(laser);
				itLaser.remove();
				continue;
			}
			
			laser.step(dt / 1000);
		}
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		
		if(!freeze) {
			
			double x = e.getX() - racket.getBounds().getWidth() / 2;
			racket.setTranslation(x, HEIGHT - 80);
			
			if(attachBalls || firstSend) {
				for(Ball ball : balls) {
					if(ball.attached)
						ball.setTranslation(x + 22, HEIGHT - 83);
				}
			}
		}
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		
		if(attachBalls)
			sendBall();
		else if(firstSend) {
			sendBall();
			firstSend = false;
		}
		
		mousePressed = true;
	}

	@Override
	public void mouseEntered(MouseEvent e) {		
	}

	@Override
	public void mouseExited(MouseEvent e) {
	}

	@Override
	public void mousePressed(MouseEvent e) {
	}

	@Override
	public void mouseReleased(MouseEvent e) {
	}
	
	private void addScore(int bonus) {
		
		score += bonus;
		camera.setScore(score);
	}
	
	private void buildLevel(int level) {
		
		this.level = level;
		LevelManager.buildLevel(this, scene, level);
	}
	
	private void cleanLevel() {

		racket.setAnimation("destroy");
		freeze = true;
		deathTimer = 0;
		
		// Supprimer les bonus à l'écran
		Iterator<Bonus> itBonus = bonuses.iterator();
		while(itBonus.hasNext()) {
			scene.removeDoodad(itBonus.next());
			itBonus.remove();
		}
		
		// Supprimer les lasers
		Iterator<Laser> itLaser = lasers.iterator();
		while(itLaser.hasNext()) {
			scene.removeDoodad(itLaser.next());
			itLaser.remove();
		}
	}
	
	private void gameOver() {
		
	}
	
	private void nextLevel() {
		
		level++;
		LevelManager.buildLevel(this, scene, level);
	}
	
	private void obtainBonus(Bonus bonus) {
		
		// Appliquer le bonus
		switch(bonus.getType()) {
		// Split into 3
		case Blue:
			placeNewBall(3);
			break;
		// Diminuer la taille
		case Brown:
			racket.setAnimation("small");
			break;
		// Augmenter la taille
		case Green:
			racket.setAnimation("large");
			break;
		// Gagner une vie
		case Grey:
			life++;
			Racket newLife = new Racket();
			newLife.setTranslation(30 * (life - 1), LIFE_OFFSET);
			newLife.setAnimation("mini");
			lifes.add(newLife);
			scene.addDoodad(newLife);
			break;
		// Grab
		case Purple:
			attachBalls = true;
			break;
		// Lasers
		case Red:
			racket.setAnimation("gun");
			fireLaser = true;
			break;
		// Score
		case Yellow:
			racket.setAnimation("large");
			break;	
		default:
			resetBonus();
		}
	}
	private void placeNewBall(int n) {
		
		for(int i = 0; i < n; ++i) {
			Ball ball = new Ball();
			ball.translate(WIDTH / 2, HEIGHT / 2, Referential.ABSOLUTE);
			ball.bounce(new Vector2D(Math.random(), Math.random()));
			balls.add(ball);
			scene.addDoodad(ball);
		}
	}
	
	private void resetBonus() {
		
		racket.setAnimation("normal");
		fireLaser = false;
		attachBalls = false;
	}

	private void restartRacket() {

		resetBonus();
		
		firstSend = true;
		
		Ball ball = new Ball();
		ball.attached = true;
		ball.setTranslation(WIDTH * 0.5 + 22, HEIGHT - 83);
		balls.add(ball);
		scene.addDoodad(ball);

		racket.setTranslation(WIDTH * 0.5, HEIGHT - 80);
	}
	
	private void sendBall() {
		
		for(Ball ball: balls) {
			if(ball.attached) {
				ball.setTranslation(ball.getTranslationX(), ball.getTranslationY() - 3);
				ball.bounce(new Vector2D(0, -1));
				ball.attached = false;
			}
		}
	}
}
